package com.hxzy.common.util;

import cn.hutool.core.date.DateUtil;
import cn.hutool.jwt.JWT;
import cn.hutool.jwt.JWTUtil;
import com.hxzy.common.enums.AckCode;
import com.hxzy.common.exception.WaimaiException;
import com.hxzy.common.properties.TokenProperty;
import com.hxzy.controller.admin.vo.resp.AdminLoginResp;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * 功能描述: 令牌工具类
 *
 * @author tonneyyy
 */
@Log4j2
@Component
public final class TokenUtil {

    public static final String MAP_UUID="uuid";

    @Autowired
    private TokenProperty tokenProperty;

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 把对象换令牌
     * @param adminLoginResp
     * @return
     */
    public String createJwt(AdminLoginResp adminLoginResp){
        //生成令牌
       adminLoginResp.setToken(UUID.randomUUID().toString());
       //设定过期时间
       LocalDateTime expiredTime = LocalDateTime.now().plusMinutes(this.tokenProperty.getAdminExpiredMinutes());
        //通过ZoneId的systemDefault获取系统默认时区
        ZoneId zoneId = ZoneId.systemDefault();
        //通过LocalDateTime的atZone(new ZoneID)把LocalDateTime转换为ZoneDateTime
        ZonedDateTime zonedDateTime =expiredTime.atZone(zoneId);
        //Instant类用于在应用程序中记录事件的时间戳
        //ZonedDateTime.toInstant是用于把ZonedDateTime转为Instant类型
        Instant instant = zonedDateTime.toInstant();
        //使用Date.from(new instant)方法,改为Date类型,该日期表示时间轴上与传递的瞬间相同的点。
       adminLoginResp.setExpiredTime( Date.from(instant));

       //存储到redis中
        String redisKey= this.tokenProperty.getAdminPrefix()+ adminLoginResp.getToken();
        this.redisTemplate.opsForValue().set(redisKey, adminLoginResp, this.tokenProperty.getAdminExpiredMinutes(), TimeUnit.MINUTES);

        // uuid再一次使用jwt来加密,hutool工具
        return uuidToJwt(adminLoginResp.getToken());
    }

    /**
     * uuid加密变为jwt
     * @param uuid
     * @return
     */
    public String uuidToJwt(String uuid){
        Map<String, Object> map = new HashMap<String, Object>() {
            private static final long serialVersionUID = 1L;
            {
                put( TokenUtil.MAP_UUID,  uuid);
            }
        };

        String jwtStr = JWTUtil.createToken(map, this.tokenProperty.getAdminJwtSecurity().getBytes());
        return jwtStr;
    }


    /**
     * 验证令牌有效性(后端)
     * @param jwtToken
     * @return
     */
    public AdminLoginResp veritifyToken(String jwtToken) {
        //1、修改令牌数据
        jwtToken=getToken(jwtToken);
        //2、jwt验证令牌有效性
        boolean jwtVerify=JWTUtil.verify(jwtToken,this.tokenProperty.getAdminJwtSecurity().getBytes(StandardCharsets.UTF_8));
        if(!jwtVerify){
            throw new WaimaiException(AckCode.APPLE_ILLEGAL);
        }
        //3、解析令牌
        try {
            JWT jwt = JWTUtil.parseToken(jwtToken);
            String uuid=jwt.getPayload(TokenUtil.MAP_UUID).toString();

            //4、redis验证令牌是否存在
            String redisKey=this.tokenProperty.getAdminPrefix()+uuid;
            Object redisValue=this.redisTemplate.opsForValue().get(redisKey);
            if(Objects.isNull(redisValue)){
                throw new WaimaiException(AckCode.APPLE_ILLEGAL);
            }
            return (AdminLoginResp) redisValue;

        }catch (Exception e){
            log.error(e);
            throw new WaimaiException(AckCode.APPLE_ILLEGAL);
        }
    }

    /**
     * 判断是否以 Bearer开头，如果有删除掉
     * @param jwtToken
     * @return
     */
    private String getToken(String jwtToken){
        if(jwtToken.startsWith(this.tokenProperty.getBearer())){
            //jwtToken=jwtToken.replace(this.tokenProperty.getBearer(),"");
            jwtToken= jwtToken.substring(this.tokenProperty.getBearer().length()+1);
        }
        return jwtToken.trim();
    }

    /**
     * 刷新令牌
     * @param adminLoginResp
     */
    public void refreshToken(AdminLoginResp adminLoginResp) {
        //判断，令牌是否快要过期了    未来时间毫秒 -  当前时间毫秒 <= 还有配置时间分钟 毫秒
        long future=adminLoginResp.getExpiredTime().getTime();
        long now=System.currentTimeMillis();
        long minutes= (future-now)/1000/60;
        if(minutes <= this.tokenProperty.getTtlMinutes()){
            adminLoginResp.setExpiredTime( DateUtil.offsetMinute(new Date(), this.tokenProperty.getAdminExpiredMinutes()));
            //存储到redis中
            String redisKey=this.tokenProperty.getAdminPrefix()+adminLoginResp.getToken();
            this.redisTemplate.opsForValue().set(redisKey, adminLoginResp, this.tokenProperty.getAdminExpiredMinutes(),TimeUnit.MINUTES);
        }
    }

    /**
     * 从redis中移出管理员的令牌
     */
    public void removeAdminToken() {
        AdminLoginResp resp=SecurityContextUtil.getAdminUser();
        String redisKey=this.tokenProperty.getAdminPrefix()+resp.getToken();
        this.redisTemplate.delete(redisKey);
    }
}
